﻿using pdfer.FileObjects;
using pdfer.Syntax;
using DEType = System.Collections.Generic.KeyValuePair<System.Int16, System.Int32>;

namespace pdfer.Filters
{
    internal class CCITTFaxDecodeFilter : IFilter
    {

        private CCITTFaxDecodeParams @params;
        public CCITTFaxDecodeParams Params { set { @params = value; } }

        public byte[] Decode(ReadOnlySpan<byte> input)
        {
            // https://www.fileformat.info/mirror/egff/ch09_05.htm
            throw new NotImplementedException();
        }

        /// <summary>
        /// 还原成标准的Tiff格式数据
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public Byte[] Recover(ReadOnlySpan<Byte> input)
        {
            // 手动构造文件头 不是解码
            // 0-1 0x49 0x49 低位在左边(小端) 0x4d 0x4d 高位在左边（大端）
            // 2-3 版本
            // 4-8 IFD偏移量 
            Byte[] head = [0x49, 0x49, 0x2A, 0x00];
            //Byte[] head1 = [0x4D, 0x4D, 0x00, 0x2A]; 大端
            var ifd = input.Length + 8;
            var ifdOffset = BitConverter.GetBytes(ifd);
            using var ms = new MemoryStream(input.Length + 8);
            ms.Write(head, 0, head.Length);
            ms.Write(ifdOffset.ToArray(), 0, 4);
            ms.Write(input);

            //ifd 结构
            // [0-1] 目录项目个数 这里指定为12 0x0C 0x00
            // 后面接着目录项(DE)
            // 目录项结构
            // [0-1] 编号
            // [2-3] 类型
            // [4-7] tag长度
            // [8-11] 如果占用的字节数少于4，则数据直接存在此处，大于4则存放指向数据的指针
            TIFFDirEntry[] des = [
                new TIFFDirEntry(TIFFDirEntry.Tag_Width, TIFFDirEntry.DEType_LONG, 1, @params.Columns),
                new TIFFDirEntry(TIFFDirEntry.Tag_Length, TIFFDirEntry.DEType_LONG, 1, @params.Rows),
                new TIFFDirEntry(TIFFDirEntry.Tag_BitsPerSample, TIFFDirEntry.DEType_SHORT, 1, 1),
                new TIFFDirEntry(TIFFDirEntry.Tag_Compression, TIFFDirEntry.DEType_SHORT, 1, @params.K <= 0 ? 3 : 4),
                new TIFFDirEntry(TIFFDirEntry.Tag_PhotometricInterpretation, TIFFDirEntry.DEType_SHORT, 1, @params.BlackIs1 ? 1 : 0),
                new TIFFDirEntry(TIFFDirEntry.Tag_StripOffsets, TIFFDirEntry.DEType_SHORT, 1, (8)),
                new TIFFDirEntry(TIFFDirEntry.Tag_SamplesPerPixel, TIFFDirEntry.DEType_SHORT, 1, 1),
                new TIFFDirEntry(TIFFDirEntry.Tag_RowsPerStrip, TIFFDirEntry.DEType_SHORT, 1, @params.Rows),
                new TIFFDirEntry(TIFFDirEntry.Tag_StripByteCounts, TIFFDirEntry.DEType_SHORT, 1, input.Length),
                new TIFFDirEntry(TIFFDirEntry.Tag_XResolution, TIFFDirEntry.DEType_RATIONAL, 1, 1),
                new TIFFDirEntry(TIFFDirEntry.Tag_YResolution, TIFFDirEntry.DEType_RATIONAL, 1, 1),
                new TIFFDirEntry(TIFFDirEntry.Tag_ResolutionUnit, TIFFDirEntry.DEType_SHORT, 1, 2)
                ];
            ms.WriteByte(0x0C); // 12
            ms.WriteByte(0x00);
            var baseOffset = ms.Position;
            ms.Position = ms.Position + des.Length * 12 + 4;
            Span<Byte> sp = stackalloc Byte[des.Length * 12];
            for(var i = 0; i < des.Length; i++)
            {
                var de = des[i];
                // 数据写入span
                de.Write(sp.Slice(12 * i, 12));
            }
            ms.Position = baseOffset;
            ms.Write(sp);
            // 四个 00 00 00 00 表示所有的IDF结束
            ms.WriteByte(0x00);
            ms.WriteByte(0x00);
            ms.WriteByte(0x00);
            ms.WriteByte(0x00);
            return ms.ToArray();
        }

        public Byte[] Encode(ReadOnlySpan<Byte> input)
        {
            throw new NotImplementedException();
        }

    }

    public class TIFFDirEntry 
    {
        public static readonly DEType DEType_Byte = KeyValuePair.Create<Int16, Int32>(1, 1);
        public static readonly DEType DEType_ASCII = KeyValuePair.Create<Int16, Int32>(2, 1);
        public static readonly DEType DEType_SHORT = KeyValuePair.Create<Int16, Int32>(3, 2);
        public static readonly DEType DEType_LONG = KeyValuePair.Create<Int16, Int32>(4, 4);
        public static readonly DEType DEType_RATIONAL = KeyValuePair.Create<Int16, Int32>(5, 8);
        public static readonly DEType DEType_SBYTE = KeyValuePair.Create<Int16, Int32>(6, 1);
        public static readonly DEType DEType_UNDEFINED = KeyValuePair.Create<Int16, Int32>(7, 1);
        public static readonly DEType DEType_SSHORT = KeyValuePair.Create<Int16, Int32>(8, 2);
        public static readonly DEType DEType_SLONG = KeyValuePair.Create<Int16, Int32>(9, 5);
        public static readonly DEType DEType_SRATIONAL = KeyValuePair.Create<Int16, Int32>(10, 8);
        public static readonly DEType DEType_FLOAT = KeyValuePair.Create<Int16, Int32>(11, 4);
        public static readonly DEType DEType_DOUBLE = KeyValuePair.Create<Int16, Int32>(12, 8);

        private static readonly DEType[] DETypes = [
                DEType_ASCII, DEType_Byte, DEType_DOUBLE,DEType_FLOAT,DEType_LONG,DEType_RATIONAL,DEType_SBYTE,
                DEType_SHORT,DEType_SLONG,DEType_SRATIONAL,DEType_DOUBLE,DEType_UNDEFINED,DEType_SSHORT
            ];

        #region Tags
        /// <summary>
        /// Short | Long
        /// </summary>
        public const Int32 Tag_Width = 0x0100;
        /// <summary>
        /// Short | Long
        ///  The number of rows (sometimes described as scanlines) in the image
        /// </summary>
        public const Int32 Tag_Length = 0x0101;
        /// <summary>
        /// Short 对于调色板图像，该值是4或者8，对于RGB图像，该值是4,4,4或8,8,8，因为这里需要填写3个数，
        /// 所以需要在这里填写一个偏移地址，然后再到偏移地址中去找这三个值。
        /// </summary>
        public const Int32 Tag_BitsPerSample = 0x0102;
        /// <summary>
        /// Short
        /// </summary>
        public const Int32 Tag_Compression = 0x0103;
        /// <summary>
        /// Short
        /// </summary>
        public const Int32 Tag_PhotometricInterpretation = 0x0106;
        /// <summary>
        /// Short | Long
        /// </summary>
        public const Int32 Tag_StripOffsets = 0x0111;
        /// <summary>
        /// Short | Long
        /// </summary>
        public const Int32 Tag_SamplesPerPixel = 0x0115;
        /// <summary>
        /// Short | Long
        /// </summary>
        public const Int32 Tag_RowsPerStrip = 0x0116;
        /// <summary>
        /// Short | Long 每个条带内包含的字节数
        /// 从字面意思来理解，这个标签应该是指每个条带内包含多少行图像数据，
        /// 因此需要看看标签273中是怎么分的条带，如果只分一个条带，那就填写图像的高度，如果每行分成一个条带，那就填写1。
        /// </summary>
        public const Int32 Tag_StripByteCounts = 0x0117;
        /// <summary>
        /// RATIONAL 宽度方向上每个分辨率单位(见标签296) 包含的像素个数
        /// </summary>
        public const Int32 Tag_XResolution = 0x011A;
        /// <summary>
        /// RATIONAL 高度方向上每个分辨率单位包含的像素个数
        /// </summary>
        public const Int32 Tag_YResolution = 0x011B;
        /// <summary>
        /// Long
        /// </summary>
        public const Int32 Tag_T4Options = 0x0124;
        /// <summary>
        /// Long
        /// </summary>
        public const Int32 Tag_T6Options = 0x0125;
        /// <summary>
        /// Short 只有3个值可选：1表示没指定单位，2表示以英寸为单位，3表示以厘米为单位。
        /// </summary>
        public const Int32 Tag_ResolutionUnit = 0x0128;
        #endregion

        private readonly Int16 tag;

        private readonly DEType type;

        private readonly Int32 count;

        private readonly Byte[] data;


        public TIFFDirEntry(Int16 tag, DEType type, Int32 count, Int32 data)
        {
            this.tag = tag;
            this.type = type;
            this.count = count;
            this.data = BitConverter.GetBytes(data);
        }

        internal void Write(Span<byte> buffSp, Int32? offset = null)
        {
            // 小端
            var tagArr = BitConverter.GetBytes(tag);       
            var typeArr = BitConverter.GetBytes(type.Key);
            var countArr = BitConverter.GetBytes(count);
            var dataArr = offset.HasValue ? BitConverter.GetBytes(offset.Value) : data;
            if (BitConverter.IsLittleEndian)
            {
                buffSp[0] = tagArr[0];
                buffSp[1] = tagArr[1];
                buffSp[2] = typeArr[0];
                buffSp[3] = typeArr[1];
                buffSp[4] = countArr[0];
                buffSp[5] = countArr[1];
                buffSp[6] = countArr[2];
                buffSp[7] = countArr[3];
                buffSp[8] = dataArr[0];
                buffSp[9] = dataArr[1];
                buffSp[10] = dataArr[2];
                buffSp[11] = dataArr[3];
            }
            else
            {
                buffSp[0] = tagArr[1];
                buffSp[1] = tagArr[0];
                buffSp[2] = typeArr[1];
                buffSp[3] = typeArr[0];
                buffSp[4] = countArr[3];
                buffSp[5] = countArr[2];
                buffSp[6] = countArr[1];
                buffSp[7] = countArr[0];
                buffSp[8] = dataArr[3];
                buffSp[9] = dataArr[2];
                buffSp[10] = dataArr[1];
                buffSp[11] = dataArr[0];
            }
        }
    }

    internal class CCITTFaxDecodeParams
    {
        public CCITTFaxDecodeParams(PdfFileObject pdfFileObject) 
        {
            if(pdfFileObject.TryGetValue(PdfName.Key_DecodeParms, out var dicObj))
            {
                var dic = PdfParserUtils.ParseDictionary(dicObj, pdfFileObject);
                if (dic.TryGetValue(PdfName.Key_K, out var obj3))
                {
                    K = PdfParserUtils.ParseInt32(obj3, pdfFileObject);
                }
                if (dic.TryGetValue(PdfName.Key_Columns, out obj3))
                {
                    Columns = PdfParserUtils.ParseInt32(obj3, pdfFileObject);
                }
                if (dic.TryGetValue(PdfName.Key_Rows, out obj3))
                {
                    Rows = PdfParserUtils.ParseInt32(obj3, pdfFileObject);
                }
                if (dic.TryGetValue(PdfName.Key_EndOfBlock, out obj3))
                {
                    EndOfBlock = PdfParserUtils.ParseBoolean(obj3, pdfFileObject);
                }
                if (dic.TryGetValue(PdfName.Key_EncodedByteAlign, out obj3))
                {
                    EncodedByteAlign = PdfParserUtils.ParseBoolean(obj3, pdfFileObject);
                }
                if (dic.TryGetValue(PdfName.Key_BlackIs1, out obj3))
                {
                    BlackIs1 = PdfParserUtils.ParseBoolean(obj3, pdfFileObject);
                }
            }
        }

        public Int32 K { get; set; } = 0;
        /// <summary>
        /// 指示编码中是否需要存在行尾位模式。
        /// CCITTFaxDecode 筛选器始终接受行尾位模式，但仅当 EndOfLine 为 true 时才需要它们。
        /// </summary>
        public Boolean EndOfLine { get; set; } = false;
        /// <summary>
        /// 指示筛选器是否在每个编码行之前需要额外的 0 位，以便该行从字节边界开始。
        /// 如果为 true，则 filter 跳过编码位以开始解码字节边界处的每一行。
        /// 如果为 false，则 filter 不需要编码表示形式中有额外的位。
        /// </summary>
        public Boolean EncodedByteAlign { get; set; } = false;
        /// <summary>
        /// 图像的宽度（以像素为单位）。
        /// 如果该值不是 8 的倍数，则过滤器会将未编码图像的宽度调整为下一个 8 倍，以便每行都从字节边界开始。
        /// </summary>
        public Int32 Columns { get; set; } = 1728;
        /// <summary>
        /// 图像的高度（以扫描线为单位）。
        /// 如果值为 0 或不存在，则图像的高度不是预先确定的，编码数据必须由块结束位模式或数据末尾终止。
        /// </summary>
        public Int32 Rows { get; set; } = 0;
        /// <summary>
        /// 指示过滤器是否期望编码数据由块结束模式终止，覆盖 Rows 参数。
        /// 如果为 false，则过滤器在解码 Rows 指示的行数或其数据已用尽时（以先发生者为准）停止。
        /// 块尾模式是适用于 K 参数的 CCITT 传真尾  块 （EOFB） 或返回控制 （RTC）。
        /// </summary>
        public Boolean EndOfBlock { get; set; } = true;
        /// <summary>
        /// 指示 1 位是否解释为黑色像素，将 0 位解释为白色像素，这与图像数据的正常 PDF 约定相反。
        /// </summary>
        public Boolean BlackIs1 { get; set; } = false;
        public Int32 DamagedRowsBeforeError { get; set; } = 0;
    }
}
